﻿namespace DimensionsHelper.Client.WinUi.Models;

public sealed partial class ProjectTask : ObservableObject, IProjectTask
{
    private static readonly object SyncRoot = new();
    private readonly ObservableCollection<MessageModel> _messages = [];

    private CancellationTokenSource? _cancellationTokenSource;
    private bool _isDisposed;
    private MessageModel? _last;
    private Task? _task;


    [ObservableProperty]
    public partial string Section { get; set; } = string.Empty;


    public IEnumerable<IMessage> Messages => _messages;


    [ObservableProperty]
    public partial string DisplayName { get; set; } = string.Empty;


    [ObservableProperty]
    public partial string Id { get; set; } = string.Empty;


    [ObservableProperty]
    public partial ReportLevel ReportLevel
    {
        get;
        set;
    }


    [ObservableProperty]
    public partial bool IsRunning
    {
        get;
        set;
    }


    [ObservableProperty]
    public partial bool CanRemove
    {
        get;
        set;
    }


    public CancellationToken CancellationToken
    {
        get
        {
            _cancellationTokenSource ??= new CancellationTokenSource();
            return _cancellationTokenSource.Token;
        }
    }


    public void Dispose()
    {
        if (_isDisposed)
        {
            return;
        }

        _task?.Dispose();
        _task = null;
        _cancellationTokenSource?.Dispose();
        _cancellationTokenSource = null;
        _isDisposed = true;

        App.GetService<IProjectTaskService>().Remove(this);
    }


    public ICommand DisposeCommand => new RelayCommand(Dispose);


    public ICommand ClearMessagesCommand => new RelayCommand(_messages.Clear);


    public ICommand ExportMessagesCommand => new RelayCommand(ExportMessages);


    public ICommand CancelCommand => new RelayCommand(
        () => _cancellationTokenSource?.Cancel(),
        () => _cancellationTokenSource != null && IsRunning);


    public void StartTask(Task task)
    {
        ObjectDisposedException.ThrowIf(_isDisposed, this);

        _task?.Dispose();
        _task = task;
        _task.ContinueWith(_ => App.DispatcherQueue.TryEnqueue(() => IsRunning = false), CancellationToken);
        IsRunning = true;
        _task.Start();
    }


    public void Log(string format, params object?[] args)
    {
        App.DispatcherQueue.TryEnqueue(() =>
        {
            lock (SyncRoot)
            {
                _last = new MessageModel(this)
                {
                    Message = string.Format(format, args),
                    Section = Section
                };

                _messages.Add(_last);
            }
        });
    }


    public void Log(ReportLevel level, string format, params object?[] args)
    {
        if (level > ReportLevel)
        {
            return;
        }

        Log(format, args);
    }


    public void Warn(string format, params object?[] args)
    {
        App.DispatcherQueue.TryEnqueue(() =>
        {
            lock (SyncRoot)
            {
                _last = new MessageModel(this)
                {
                    Message = string.Format(format, args),
                    Section = Section,
                    Type = MessageType.Warning
                };

                _messages.Add(_last);
            }
        });
    }


    public void Error(string format, params object?[] args)
    {
        App.DispatcherQueue.TryEnqueue(() =>
        {
            lock (SyncRoot)
            {
                _last = new MessageModel(this)
                {
                    Message = string.Format(format, args),
                    Section = Section,
                    Type = MessageType.Error
                };

                _messages.Add(_last);
            }
        });
    }


    public void Progress(double progress)
    {
        App.DispatcherQueue.TryEnqueue(() =>
        {
            lock (SyncRoot)
            {
                if (_last?.WithProgress == true && _last.Progress < progress)
                {
                    _last.Progress = progress;
                }
            }
        });
    }


    public void Progress(double progress, string format, params object?[] args)
    {
        App.DispatcherQueue.TryEnqueue(() =>
        {
            lock (SyncRoot)
            {
                string message = string.Format(format, args);

                if (_last?.WithProgress is true && _last.Progress < progress)
                {
                    _last.Message = message;
                    _last.Progress = progress;
                }
                else
                {
                    _last = new MessageModel(this)
                    {
                        Message = message,
                        Section = Section,
                        Progress = progress,
                        WithProgress = true,
                        Type = MessageType.Information
                    };

                    _messages.Add(_last);
                }
            }
        });
    }

    public IProgressEnumerable<T> WithProgress<T>(IEnumerable<T> source, int length,
        Func<T, double, string>? messageGetter)
    {
        var model = new MessageModel(this)
        {
            Section = Section,
            WithProgress = true,
            Type = MessageType.Information
        };

        App.DispatcherQueue.TryEnqueue(() =>
        {
            lock (SyncRoot)
            {
                _last = model;
                _messages.Add(_last);
            }
        });

        return new ProjectTaskProgressEnumerable<T>(model, source, length, messageGetter);
    }


    public void Directory(string localDirectory)
    {
        App.DispatcherQueue.TryEnqueue(() =>
        {
            lock (SyncRoot)
            {
                _last = new MessageModel(this)
                {
                    LocalDirectory = localDirectory
                };

                _messages.Add(_last);
            }
        });
    }


    public void Directory(string localDirectory, string format, params object?[] args)
    {
        App.DispatcherQueue.TryEnqueue(() =>
        {
            lock (SyncRoot)
            {
                _last = new MessageModel(this)
                {
                    Message = string.Format(format, args),
                    LocalDirectory = localDirectory,
                    Section = Section
                };

                _messages.Add(_last);
            }
        });
    }


    private string BuildDisplayName(string id) => IsRunning ? $"[正在运行] {id}" : id;


    partial void OnIdChanged(string value) => DisplayName = BuildDisplayName(value);


    partial void OnIsRunningChanged(bool value) => DisplayName = BuildDisplayName(Id);


    private async void ExportMessages()
    {
        try
        {
            if (_messages.Count == 0)
            {
                await App.GetService<IMessageService>().ShowAsync("没有可导出的消息", null);
                return;
            }

            string? targetFile = await App.GetService<IFilePikerService>().SaveFileAsync(FileFilters.Text);

            if (string.IsNullOrEmpty(targetFile))
            {
                return;
            }

            await File.WriteAllTextAsync(targetFile, string.Join("\r\n", _messages.Where(m => !m.WithProgress)
                .Select(m => $"[{m.Section}] {m.Message}")), CancellationToken);
        }
        catch (Exception e)
        {
            Error(e.Message);
        }
    }
}